/* Time-stamp: <2009-11-14 18:39:10 xinhaoyuan> */
/* user init function and user interface, syscall implementation */

#define _EKOS_KERNEL_C_

#include <kernel/kernel.h>
#include <inc/syscall.h>

int
user_init(void)
{
	 /* 建立初始的 ustack */
	 uint32_t perm = PTE_U | PTE_W;
	 fix_addr((void *)(USTACK_TOP - 1), &perm, true);
	 
     return 0;
}

/* simple warpper */
void
user_start(void *addr)
{
     asm volatile ("movl %0, %%edx\n jmp jump_user" :: "r"(addr));
}

void
sc_monitor(struct trapframe_t *tf)
{
	 monitor(NULL);
}

void
sc_exit(struct trapframe_t *tf)
{
	 env_destroy();
}

/* 以下是系统调用 */
/* ret: err no */
void
sc_fork(struct trapframe_t *tf)
{
     struct env_t *env = envs + cur_env;
     if (env->xinfo.level == 0)
     {
		  fork(tf);
		  if (tf->regs.eax != 0) cprintf("fork: %e\n", tf->regs.eax);
     }
     else tf->regs.eax = -E_BAD_ENV;
}

/* ret: err no */
void
sc_sfork(struct trapframe_t *tf)
{
     struct env_t *env = envs + cur_env;
     if (env->xinfo.level == 0)
     {
		  int origin = cur_env;
		  sfork(tf);
		  if (tf->regs.eax == 0 &&
			  cur_env == origin)
		  {
			   if (sch_sleep(cur_env, NULL) == 0)
					call_yield();
		  }		  
     }
     else tf->regs.eax = -E_BAD_ENV;
}


/* ret: err no */
/* void */
/* sc_dumb_fork(struct trapframe_t *tf) */
/* { */
/*      /\* struct env_t *env = envs + cur_env; *\/ */
/*      /\* if (env->xinfo.level == 0 && env->rpc_level == 0) *\/ */
/*      /\* { *\/ */
/* 	 /\* 	  expand_addr_space(); *\/ */
/* 	 /\* 	  dumb_fork(tf); *\/ */
/*      /\* } *\/ */
/*      /\* else *\/ tf->regs.eax = -E_BAD_ENV; */
/* } */

/* ret: 当前环境对应的索引 */
void
sc_cur_env(struct trapframe_t *tf)
{
     tf->regs.eax = (uint32_t)cur_env;
}

/* arg0: vaddr of format */
/* arg1: vaddr of va_list */
void
sc_kprintf(struct trapframe_t *tf)
{
     const char *fmt = (const char *)tf->regs.ebx;
     va_list va = (va_list)tf->regs.ecx;

	 // EFLAGS_T f = intr_disable_begin();
	 
     vkprintf(fmt, va);

	 // intr_disable_end(f);
}

/* skip to next env */
void
sc_wait(struct trapframe_t *tf)
{
     sch_yield(ENV_SCH_WAITING);
}

// arg0 : the status of the process
// ret  : meanless
void
sc_waitb(struct trapframe_t *tf)
{
	 sch_yield((uint32_t)tf->regs.ebx);
}

/* arg0: vaddr of the pfhandler */
/* ret : err */
void
sc_set_pfhandler(struct trapframe_t *tf)
{
	 struct env_t *env = envs + cur_env;
	 uint32_t pfhandler = (uint32_t)tf->regs.ebx;
	 dyninfo.upfhandler = pfhandler;
	 
	 tf->regs.eax = 0;
}

/* arg0: vaddr which need to be fixed */
/* ret : err */
void
sc_fix_addr(struct trapframe_t *tf)
{
	 void *addr = (void *)tf->regs.ebx;
	 tf->regs.eax = fix_addr(addr, NULL);
}

/* arg0: vaddr needed to be mapped */
/* arg1: perm */
/* ret: err */
void
sc_alloc_page(struct trapframe_t *tf)
{
     void *pte;
     uint32_t perm;
     uint32_t value;
     uint32_t l;

     pte = (void *)PAGE_ALIGN(tf->regs.ebx);

     if (pte >= (void *)UMAP_TOP)
     {
		  tf->regs.eax = -E_INVAL;
		  return;
     }

     l = pcs_push_level();

     physaddr_t naddr;
     if ((tf->regs.eax = frame_alloc_push(&naddr)) != 0)
     {
		  return;
     }
     
     perm = tf->regs.ecx & PTE_USER;

     uint32_t v = naddr | perm;

     // kprintf(" create page %08x = %08x with perm %b\n", pte, naddr, perm);
	 
     if ((tf->regs.eax = fix_addr(pte, &v, true)) != 0)
     {
		  pcs_clear_level(l);
		  return;
     }
	 
	 frame_dec_ref(PTE_ADDR(v));

     tf->regs.eax = 0;
	 
     pcs_pop_level(l);
     tlbflush();
}

/* This function is used to allocate public resource for the process,
 * if the function success, kernel will give the mapping privilage for
 * the process */
/* arg0: vaddr for mapping */
/* ret: err */
/* void */
/* sc_alloc_shared_page(struct trapframe_t *tf) */
/* { */
/* 	 /\* TODO -- we may check the resource limit, or privilage */
/* 	  * checking *\/ */
	 
/* 	 void *vaddr = tf->regs.ebx; */

/* 	 physaddr_t naddr; */
/* 	 if (frame_alloc(&naddr) != 0) */
/* 	 { */
/* 		  tf->regs.eax = 1; */
/* 		  return; */
/* 	 } */

/* 	 frame */
/* } */

/* arg0: vaddr needed to be unmapped */
/* ret : err */
void
sc_remove_page(struct trapframe_t *tf)
{
     void *addr;

     addr = (void *)PAGE_ALIGN(tf->regs.ebx);

     if (addr >= (void *)UMAP_TOP)
     {
		  tf->regs.eax = -E_INVAL;
		  return ;
     }

     if ((tf->regs.eax = fix_addr(addr, NULL)))
	 {
		  return;
	 }

	 uint32_t idx = PAGE_NUM(addr);
	 ptab_entry_t pte = lock_pte(idx);

	 if (pte & PTE_P)
	 {
		  frame_dec_ref(PTE_ADDR(pte));
	 }

	 release_pte(idx, 0);
	 tf->regs.eax = 0;
}

/* arg0: 0 -- cancel the admin perm, !0 -- get the admin perm */
/* ret: err no */
void
sc_set_adm(struct trapframe_t *tf)
{
     if (tf->regs.ebx == 0)
     {
		  unset_adm();
		  tf->regs.eax = 0;
     }
     else
     {
		  /* TODO perm check */
		  set_adm();
		  tf->regs.eax = 0;
     }
}

/* arg0: 0 -- cancel the svp perm, !0 -- get the svp perm */
/* ret: err no */
void
sc_set_svp(struct trapframe_t *tf)
{
     if (tf->regs.ebx == 0)
     {
		  unset_svp();
		  tf->regs.eax = 0;
     }
     else
     {
		  /* TODO perm check */
		  set_svp();
		  tf->regs.eax = 0;
     }
}

// arg0: vaddr of the shared page
// arg1: 0 -- detached shareing
// ret: the physaddr of the memory, 0 for error
void
sc_share_page(struct trapframe_t *tf)
{
	 void *addr = (void *)tf->regs.ebx;
	
	 if (addr < (void *)UMAP_TOP)
	 {
		  if (fix_addr(addr, NULL) == 0)
		  {
			   int idx = PAGE_NUM(addr);
			   ptab_entry_t pte = lock_pte(idx);
			   physaddr_t result = PTE_ADDR(pte);
			   
			   tf->regs.eax =
					frame_share(result) == 0 ? result : 0;

			   release_pte(idx, pte);
		  }
		  else tf->regs.eax = 0;
	 }
	 else tf->regs.eax = 0;
}

// arg0: vaddr of the shared page
// arg1: paddr of the page, with flag
// ret: err
void
sc_shared_map(struct trapframe_t *tf)
{
	 void *addr = (void *)tf->regs.ebx;
	 physaddr_t pa = tf->regs.ecx;

	 if (addr < (void *)UMAP_TOP && frame_shared_p((physaddr_t)PAGE_ALIGN(pa)))
	 {
		  fix_addr(addr, NULL);
		  
		  int idx = PAGE_NUM(addr);
		  
		  ptab_entry_t pte = lock_pte(idx);
		  
		  frame_inc_ref(PTE_ADDR(pa));
		  if (pte & PTE_P)
			   frame_dec_ref(PTE_ADDR(pte));
		  pte = pa | PTE_P;
		  
		  release_pte(idx, pte);
	 }
	 else tf->regs.eax = 0;
}

/* arg0: 等待环头索引 */
/* ret : err */
void
sc_sleep(struct trapframe_t *tf)
{
	 uint16_t idx = (uint16_t)tf->regs.ebx;
	 uint8_t *lock = (uint8_t *)tf->regs.ecx;

	 if (idx >= ENVS_MAXCOUNT)
	 {
		  tf->regs.eax = -E_INVAL;
	 }
	 else
	 {
		  tf->regs.eax = sch_sleep(idx, lock);
	 }

	 if (tf->regs.eax == 0)
		  call_yield();
}

/* arg0: 需要唤醒的索引 */
/* ret : err */
void
sc_wakeup(struct trapframe_t *tf)
{
	 uint16_t idx = (uint16_t)tf->regs.ebx;
	 uint8_t *lock = (uint8_t *)tf->regs.ecx;

	 if (idx >= ENVS_MAXCOUNT)
	 {
		  tf->regs.eax = -E_INVAL;
	 }
	 else
	 {
		  tf->regs.eax = sch_wakeup(idx, lock);
	 }
}

void
sc_iec_send(struct trapframe_t *tf)
{
	 struct env_t *env = envs + cur_env;
	 sl_lock(&env->iec.send.lock);

	 if (env->iec.send.to != cur_env)
		  tf->regs.eax = -E_IEC_BUSY;
	 else tf->regs.eax = 0;
	 
	 sl_release(&env->iec.send.lock);

	 if (tf->regs.eax != 0) return;

	 env->iec.cache.arg_a = (uint32_t)tf->regs.ecx;
	 env->iec.cache.arg_b = (uint32_t)tf->regs.edx;
	 env->iec.cache.arg_c = (uint32_t)tf->regs.esi;
	 env->iec.cache.arg_d = (uint32_t)tf->regs.edi;

	 tf->regs.eax = iec_send((uint16_t)tf->regs.ebx);
}

void
sc_iec_send_free(struct trapframe_t *tf)
{
	 struct env_t *env = envs + cur_env;

	 sl_lock(&env->iec.send.lock);

	 if (env->iec.send.to != cur_env)
		  tf->regs.eax = -E_IEC_BUSY;
	 else tf->regs.eax = 0;
	 
	 sl_release(&env->iec.send.lock);	 
}

void
sc_iec_recv(struct trapframe_t *tf)
{
	 tf->regs.eax = iec_recv((uint16_t)tf->regs.ebx);
}

uint16_t user_service_table[256];

void
sc_set_user_service(struct trapframe_t *tf)
{
	 uint32_t num = tf->regs.ebx;
	 uint16_t idx = tf->regs.ecx;

	 /* TODO - permission check */

	 user_service_table[num] = idx;
	 tf->regs.eax = 0;
}

void
sc_get_user_service(struct trapframe_t *tf)
{
	 uint32_t num = tf->regs.ebx;
	 tf->regs.eax = user_service_table[num];
}

void
sc_read_klog_pipe(struct trapframe_t *tf)
{
	 void *ptr = (void *)tf->regs.ebx;
	 uint32_t n = tf->regs.ecx;
	 
	 uint32_t result;
	 if (log_enabled)
		  /* TODO - permission and range check */
	 {
		  clear_area(ptr, (char *)ptr + n);
		  NOINTR_S;
		  result = kpipe_read_unsafe(&klog_pipe, ptr, n);
		  NOINTR_E;
	 }
	 else result = 0;
	 
	 tf->regs.eax = result;
}

void
sc_read_irq_pipe(struct trapframe_t *tf)
{
	 uint32_t num = tf->regs.ebx;
	 void *ptr = (void *)tf->regs.ecx;
	 uint32_t n = tf->regs.edx;

	 /* TODO - permission and range check */
	 uint32_t result;
	 if (irq_pipes[num] != NULL)
	 {
		  clear_area(ptr, (char *)ptr + n);
		  NOINTR_S;
		  result = kpipe_read_unsafe(irq_pipes[num], ptr, n);
		  NOINTR_E;
	 }
	 else result = 0;
	 
	 tf->regs.eax = result;
}

void
sc_wait_irq(struct trapframe_t *tf)
{
	 uint32_t num = tf->regs.ebx;

	 /* TODO - permission and range check */
	 NOINTR_S;
	 semaphore_wait(irq_semaphores + num);
	 NOINTR_E;
	 
	 tf->regs.eax = 0;
}

static ex_handler_t sc_table[SC_MAX + 1] =
{
	 [SC_MONITOR]       = &sc_monitor,

	 [SC_EXIT]          = &sc_exit,
	 
     [SC_FORK]			= &sc_fork,
     // [SC_DUMB_FORK]	= &sc_dumb_fork,
	 [SC_SFORK]         = &sc_sfork,

	 [SC_SET_PFHANDLER] = &sc_set_pfhandler,
     [SC_CUR_ENV]		= &sc_cur_env,
     [SC_KPRINTF]		= &sc_kprintf,
     [SC_WAIT]			= &sc_wait,
	 [SC_WAITB]         = &sc_waitb,
	 [SC_FIX_ADDR]      = &sc_fix_addr,
     [SC_ALLOC_PAGE]	= &sc_alloc_page,
     [SC_REMOVE_MAP]	= &sc_remove_page,

     [SC_SET_ADM]		= &sc_set_adm,
	 [SC_SET_SVP]		= &sc_set_svp,

	 [SC_SLEEP]         = &sc_sleep,
	 [SC_WAKEUP]        = &sc_wakeup,

	 [SC_IEC_SEND]		= &sc_iec_send,
	 [SC_IEC_SEND_FREE] = &sc_iec_send_free,
	 [SC_IEC_RECV]		= &sc_iec_recv,

	 [SC_SHARED_MAP]    = &sc_shared_map,
	 [SC_SHARE_PAGE]    = &sc_share_page,

	 [SC_SET_USER_SERVICE] = &sc_set_user_service,
	 [SC_GET_USER_SERVICE] = &sc_get_user_service,

	 [SC_READ_KLOG_PIPE] = &sc_read_klog_pipe,
	 [SC_READ_IRQ_PIPE] = &sc_read_irq_pipe,

	 [SC_WAIT_IRQ] = &sc_wait_irq,
	 
};

void
syscall_handler(struct trapframe_t tf)
{
     
     if (tf.num > SC_MAX)
     {
		  tf.regs.eax = -E_UNSPECIFIED;
		  if (envs[cur_env].eid == 0)
			   while (1) ;
     }
     else
     {
		  struct env_t *env = envs + cur_env;
		  env->inkern = 1;
		  
		  if (env->xinfo.level > 0)
		  {
			   /* 当处在用户异常态的时候保存状态 */
			   char *old_stack;
			   old_stack = (void *)tf.stk.esp;
			   old_stack -= sizeof(uint32_t);
			   
			   *(uint32_t *)old_stack = env->xinfo.usp;
			   env->xinfo.usp = (uint32_t)old_stack;

			   sc_table[tf.num](&tf);
	       
			   tf.stk.esp = env->xinfo.usp + sizeof(uint32_t);
			   env->xinfo.usp = *(uint32_t *)(env->xinfo.usp);
		  }
		  else sc_table[tf.num](&tf);
		  env->inkern = 0;
	 }
}
